Skip to content

Conversation

@clayborg
Copy link
Collaborator

@clayborg clayborg commented Jun 3, 2025

Users can implement a JIT loader in python by implementing a class that named "JITLoaderPlugin" in a module. The path to this module is specified in the settings using:

 (lldb) setting set target.process.python-jit-loader-path <path>

When the process starts up it will load this module and call methods on the python class. The python class must implement the following methods:

class JITLoaderPlugin:
    #-----------------------------------------------------------------------
    # Construct this object with reference to the process that owns this
    # JIT loader.
    #-----------------------------------------------------------------------
    def __init__(self, process: lldb.SBProcess):
        self.process = process

    #-----------------------------------------------------------------------
    # Called when attaching is completed.
    #-----------------------------------------------------------------------
    def did_attach(self):
        pass

    #-----------------------------------------------------------------------
    # Called when launching is completed.
    #-----------------------------------------------------------------------
    def did_launch(self):
        pass

    #-----------------------------------------------------------------------
    # Called once for each module that is loaded into the debug sessions.
    # This allows clients to search to symbols or references to JIT'ed
    # functions in each module as it gets loaded. Note that this function
    # can be called prior to did_attach() or did_launch() being called as
    # libraries get loaded during the attach or launch.
    #-----------------------------------------------------------------------
    def module_did_load(self, module: lldb.SBModule):
        pass

Users can implement a JIT loader in python by implementing a class that named "JITLoaderPlugin" in a module. The path to this module is specified in the settings using:

     (lldb) setting set target.process.python-jit-loader-path <path>

When the process starts up it will load this module and call methods on the python class. The python class must implement the following methods:

```
class JITLoaderPlugin:
    #-----------------------------------------------------------------------
    # Construct this object with reference to the process that owns this
    # JIT loader.
    #-----------------------------------------------------------------------
    def __init__(self, process: lldb.SBProcess):
        self.process = process

    #-----------------------------------------------------------------------
    # Called when attaching is completed.
    #-----------------------------------------------------------------------
    def did_attach(self):
        pass

    #-----------------------------------------------------------------------
    # Called when launching is completed.
    #-----------------------------------------------------------------------
    def did_launch(self):
        pass

    #-----------------------------------------------------------------------
    # Called once for each module that is loaded into the debug sessions.
    # This allows clients to search to symbols or references to JIT'ed
    # functions in each module as it gets loaded. Note that this function
    # can be called prior to did_attach() or did_launch() being called as
    # libraries get loaded during the attach or launch.
    #-----------------------------------------------------------------------
    def module_did_load(self, module: lldb.SBModule):
        pass

```
@llvmbot
Copy link
Member

llvmbot commented Jun 3, 2025

@llvm/pr-subscribers-lldb

Author: Greg Clayton (clayborg)

Changes

Users can implement a JIT loader in python by implementing a class that named "JITLoaderPlugin" in a module. The path to this module is specified in the settings using:

 (lldb) setting set target.process.python-jit-loader-path &lt;path&gt;

When the process starts up it will load this module and call methods on the python class. The python class must implement the following methods:

class JITLoaderPlugin:
    #-----------------------------------------------------------------------
    # Construct this object with reference to the process that owns this
    # JIT loader.
    #-----------------------------------------------------------------------
    def __init__(self, process: lldb.SBProcess):
        self.process = process

    #-----------------------------------------------------------------------
    # Called when attaching is completed.
    #-----------------------------------------------------------------------
    def did_attach(self):
        pass

    #-----------------------------------------------------------------------
    # Called when launching is completed.
    #-----------------------------------------------------------------------
    def did_launch(self):
        pass

    #-----------------------------------------------------------------------
    # Called once for each module that is loaded into the debug sessions.
    # This allows clients to search to symbols or references to JIT'ed
    # functions in each module as it gets loaded. Note that this function
    # can be called prior to did_attach() or did_launch() being called as
    # libraries get loaded during the attach or launch.
    #-----------------------------------------------------------------------
    def module_did_load(self, module: lldb.SBModule):
        pass


Patch is 40.07 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/142514.diff

28 Files Affected:

  • (modified) lldb/bindings/python/python-wrapper.swig (+12)
  • (modified) lldb/include/lldb/API/SBModule.h (+1)
  • (added) lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h (+30)
  • (modified) lldb/include/lldb/Interpreter/ScriptInterpreter.h (+8)
  • (modified) lldb/include/lldb/Target/Process.h (+1)
  • (modified) lldb/include/lldb/lldb-forward.h (+2)
  • (modified) lldb/source/Interpreter/ScriptInterpreter.cpp (+6)
  • (modified) lldb/source/Plugins/JITLoader/CMakeLists.txt (+1)
  • (added) lldb/source/Plugins/JITLoader/Python/CMakeLists.txt (+11)
  • (added) lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp (+146)
  • (added) lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h (+62)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt (+1-2)
  • (added) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp (+79)
  • (added) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h (+104)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h (+1)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp (+18)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h (+9)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h (+1)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp (+5)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h (+2)
  • (modified) lldb/source/Target/Process.cpp (+6)
  • (modified) lldb/source/Target/TargetProperties.td (+3)
  • (added) lldb/test/API/functionalities/jitloader_python/Makefile (+10)
  • (added) lldb/test/API/functionalities/jitloader_python/TestJITLoaderPython.py (+105)
  • (added) lldb/test/API/functionalities/jitloader_python/jit.cpp (+44)
  • (added) lldb/test/API/functionalities/jitloader_python/jit_loader.py (+79)
  • (added) lldb/test/API/functionalities/jitloader_python/main.cpp (+48)
  • (modified) lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp (+5)
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 3d1d04e47e70b..9e8528357bf76 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -542,6 +542,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject * data
   return sb_ptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBModule(PyObject * data) {
+  lldb::SBModule *sb_ptr = nullptr;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBModule, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBStream(PyObject * data) {
   lldb::SBStream *sb_ptr = nullptr;
 
diff --git a/lldb/include/lldb/API/SBModule.h b/lldb/include/lldb/API/SBModule.h
index 85332066ee687..7f4e9ef470532 100644
--- a/lldb/include/lldb/API/SBModule.h
+++ b/lldb/include/lldb/API/SBModule.h
@@ -306,6 +306,7 @@ class LLDB_API SBModule {
   friend class SBType;
 
   friend class lldb_private::python::SWIGBridge;
+  friend class lldb_private::ScriptInterpreter;
 
   explicit SBModule(const lldb::ModuleSP &module_sp);
 
diff --git a/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h
new file mode 100644
index 0000000000000..b18d7ae5ae3f5
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h
@@ -0,0 +1,30 @@
+//===-- JITLoaderInterface.h ------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_INTERFACES_JITLOADERINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_JITLOADERINTERFACE_H
+
+#include "ScriptedThreadInterface.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+class JITLoaderInterface : virtual public ScriptedInterface {
+public:
+
+  virtual llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name, 
+                     lldb_private::ExecutionContext &exe_ctx) = 0;
+
+  virtual void DidAttach() {};
+  virtual void DidLaunch() {};
+  virtual void ModulesDidLoad(lldb_private::ModuleList &module_list) {};
+
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_JITLOADERINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 3a4a7ae924584..99531844c80f3 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -16,6 +16,7 @@
 #include "lldb/API/SBEvent.h"
 #include "lldb/API/SBExecutionContext.h"
 #include "lldb/API/SBLaunchInfo.h"
+#include "lldb/API/SBModule.h"
 #include "lldb/API/SBMemoryRegionInfo.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/Breakpoint/BreakpointOptions.h"
@@ -24,6 +25,7 @@
 #include "lldb/Core/ThreadedCommunication.h"
 #include "lldb/Host/PseudoTerminal.h"
 #include "lldb/Host/StreamFile.h"
+#include "lldb/Interpreter/Interfaces/JITLoaderInterface.h"
 #include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h"
@@ -560,6 +562,10 @@ class ScriptInterpreter : public PluginInterface {
     return {};
   }
 
+  virtual lldb::JITLoaderInterfaceSP CreateJITLoaderInterface() {
+    return {};
+  }
+
   virtual lldb::ScriptedPlatformInterfaceUP GetScriptedPlatformInterface() {
     return {};
   }
@@ -597,6 +603,8 @@ class ScriptInterpreter : public PluginInterface {
   lldb::ExecutionContextRefSP GetOpaqueTypeFromSBExecutionContext(
       const lldb::SBExecutionContext &exe_ctx) const;
 
+  lldb::ModuleSP GetOpaqueTypeFromSBModule(const lldb::SBModule &module) const;
+  
 protected:
   Debugger &m_debugger;
   lldb::ScriptLanguage m_script_lang;
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 536a69fb89759..305a682f0ad83 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -87,6 +87,7 @@ class ProcessProperties : public Properties {
   Args GetExtraStartupCommands() const;
   void SetExtraStartupCommands(const Args &args);
   FileSpec GetPythonOSPluginPath() const;
+  FileSpec GetPythonJITLoaderPath() const;
   uint32_t GetVirtualAddressableBits() const;
   void SetVirtualAddressableBits(uint32_t bits);
   uint32_t GetHighmemVirtualAddressableBits() const;
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index c664d1398f74d..837c734a98059 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -114,6 +114,7 @@ class Instruction;
 class InstructionList;
 class InstrumentationRuntime;
 class JITLoader;
+class JITLoaderInterface;
 class JITLoaderList;
 class Language;
 class LanguageCategory;
@@ -364,6 +365,7 @@ typedef std::shared_ptr<lldb_private::IOHandler> IOHandlerSP;
 typedef std::shared_ptr<lldb_private::IOObject> IOObjectSP;
 typedef std::shared_ptr<lldb_private::IRExecutionUnit> IRExecutionUnitSP;
 typedef std::shared_ptr<lldb_private::JITLoader> JITLoaderSP;
+typedef std::shared_ptr<lldb_private::JITLoaderInterface> JITLoaderInterfaceSP;
 typedef std::unique_ptr<lldb_private::JITLoaderList> JITLoaderListUP;
 typedef std::shared_ptr<lldb_private::LanguageRuntime> LanguageRuntimeSP;
 typedef std::unique_ptr<lldb_private::SystemRuntime> SystemRuntimeUP;
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 63655cc5a50c6..0b12425c1c504 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -130,6 +130,12 @@ ScriptInterpreter::GetOpaqueTypeFromSBExecutionContext(
   return exe_ctx.m_exe_ctx_sp;
 }
 
+lldb::ModuleSP
+ScriptInterpreter::GetOpaqueTypeFromSBModule(
+    const lldb::SBModule &module) const {
+  return module.m_opaque_sp;
+}
+
 lldb::ScriptLanguage
 ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) {
   if (language.equals_insensitive(LanguageToString(eScriptLanguageNone)))
diff --git a/lldb/source/Plugins/JITLoader/CMakeLists.txt b/lldb/source/Plugins/JITLoader/CMakeLists.txt
index e52230199109f..aad64f1e7c8bd 100644
--- a/lldb/source/Plugins/JITLoader/CMakeLists.txt
+++ b/lldb/source/Plugins/JITLoader/CMakeLists.txt
@@ -1 +1,2 @@
 add_subdirectory(GDB)
+add_subdirectory(Python)
diff --git a/lldb/source/Plugins/JITLoader/Python/CMakeLists.txt b/lldb/source/Plugins/JITLoader/Python/CMakeLists.txt
new file mode 100644
index 0000000000000..a64ade6009a2b
--- /dev/null
+++ b/lldb/source/Plugins/JITLoader/Python/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_lldb_library(lldbPluginJITLoaderPython PLUGIN
+  JITLoaderPython.cpp
+
+  LINK_LIBS
+    lldbCore
+    lldbInterpreter
+    lldbSymbol
+    lldbTarget
+    lldbValueObject
+    lldbPluginProcessUtility
+  )
diff --git a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp
new file mode 100644
index 0000000000000..8981d55fc4661
--- /dev/null
+++ b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp
@@ -0,0 +1,146 @@
+//===-- JITLoaderPython.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "JITLoaderPython.h"
+
+#include "Plugins/Process/Utility/RegisterContextDummy.h"
+#include "Plugins/Process/Utility/RegisterContextMemory.h"
+#include "Plugins/Process/Utility/ThreadMemory.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Symbol/ObjectFile.h"
+#include "lldb/Symbol/VariableList.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/StopInfo.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Target/ThreadList.h"
+#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/RegisterValue.h"
+#include "lldb/Utility/StreamString.h"
+#include "lldb/Utility/StructuredData.h"
+#include "lldb/ValueObject/ValueObjectVariable.h"
+
+#include <memory>
+
+using namespace lldb;
+using namespace lldb_private;
+
+LLDB_PLUGIN_DEFINE(JITLoaderPython)
+
+void JITLoaderPython::Initialize() {
+  PluginManager::RegisterPlugin(GetPluginNameStatic(),
+                                GetPluginDescriptionStatic(), CreateInstance,
+                                nullptr);
+}
+
+void JITLoaderPython::Terminate() {
+  PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+JITLoaderSP JITLoaderPython::CreateInstance(Process *process, bool force) {
+  // Python JITLoader plug-ins must be requested by name, so force must
+  // be true
+  FileSpec python_os_plugin_spec(process->GetPythonJITLoaderPath());
+  if (python_os_plugin_spec &&
+      FileSystem::Instance().Exists(python_os_plugin_spec)) {
+    std::unique_ptr<JITLoaderPython> os_up(
+        new JITLoaderPython(process, python_os_plugin_spec));
+    if (os_up.get() && os_up->IsValid())
+      return os_up;
+  }
+  return nullptr;
+}
+
+llvm::StringRef JITLoaderPython::GetPluginDescriptionStatic() {
+  return "JIT loader plug-in that implements a JIT loader using a python "
+         "class that implements the necessary JITLoader functionality.";
+}
+
+JITLoaderPython::JITLoaderPython(lldb_private::Process *process,
+                                 const FileSpec &python_module_path)
+    : JITLoader(process), m_interpreter(nullptr), m_script_object_sp() {
+  if (!process)
+    return;
+  TargetSP target_sp = process->CalculateTarget();
+  if (!target_sp)
+    return;
+  m_interpreter = target_sp->GetDebugger().GetScriptInterpreter();
+  if (!m_interpreter)
+    return;
+
+  std::string os_plugin_class_name(
+      python_module_path.GetFilename().AsCString(""));
+  if (os_plugin_class_name.empty())
+    return;
+
+  LoadScriptOptions options;
+  char python_module_path_cstr[PATH_MAX];
+  python_module_path.GetPath(python_module_path_cstr,
+                             sizeof(python_module_path_cstr));
+  Status error;
+  if (!m_interpreter->LoadScriptingModule(python_module_path_cstr, options,
+                                          error))
+    return;
+
+  // Strip the ".py" extension if there is one
+  size_t py_extension_pos = os_plugin_class_name.rfind(".py");
+  if (py_extension_pos != std::string::npos)
+    os_plugin_class_name.erase(py_extension_pos);
+  // Add ".JITLoaderPlugin" to the module name to get a string like
+  // "modulename.JITLoaderPlugin"
+  os_plugin_class_name += ".JITLoaderPlugin";
+
+  JITLoaderInterfaceSP interface_sp =
+      m_interpreter->CreateJITLoaderInterface();
+  if (!interface_sp)
+    return;
+
+  ExecutionContext exe_ctx(process);
+  auto obj_or_err = interface_sp->CreatePluginObject(
+      os_plugin_class_name, exe_ctx);
+
+  if (!obj_or_err) {
+    llvm::consumeError(obj_or_err.takeError());
+    return;
+  }
+
+  StructuredData::GenericSP owned_script_object_sp = *obj_or_err;
+  if (!owned_script_object_sp->IsValid())
+    return;
+
+  m_script_object_sp = owned_script_object_sp;
+  m_interface_sp = interface_sp;
+}
+
+JITLoaderPython::~JITLoaderPython() = default;
+
+void JITLoaderPython::DidAttach() {
+  if (m_interface_sp)
+    m_interface_sp->DidAttach();
+}
+
+void JITLoaderPython::DidLaunch() {
+  if (m_interface_sp)
+    m_interface_sp->DidLaunch();
+}
+
+void JITLoaderPython::ModulesDidLoad(lldb_private::ModuleList &module_list) {
+  if (m_interface_sp)
+    m_interface_sp->ModulesDidLoad(module_list);
+}
+
+#endif // #if LLDB_ENABLE_PYTHON
diff --git a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h
new file mode 100644
index 0000000000000..87146ad216de3
--- /dev/null
+++ b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h
@@ -0,0 +1,62 @@
+//===-- JITLoaderPython.h ---------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_JITLoaderPython_h_
+#define liblldb_JITLoaderPython_h_
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "lldb/Target/JITLoader.h"
+#include "lldb/Utility/StructuredData.h"
+
+namespace lldb_private {
+class ScriptInterpreter;
+}
+
+class JITLoaderPython : public lldb_private::JITLoader {
+public:
+  JITLoaderPython(lldb_private::Process *process,
+                  const lldb_private::FileSpec &python_module_path);
+
+  ~JITLoaderPython() override;
+
+  // Static Functions
+  static lldb::JITLoaderSP CreateInstance(lldb_private::Process *process, 
+                                          bool force);
+
+  static void Initialize();
+
+  static void Terminate();
+
+  static llvm::StringRef GetPluginNameStatic() { return "python"; }
+
+  static llvm::StringRef GetPluginDescriptionStatic();
+
+  // lldb_private::PluginInterface Methods
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+
+  // lldb_private::JITLoader Methods
+  void DidAttach() override;
+  void DidLaunch() override;
+  void ModulesDidLoad(lldb_private::ModuleList &module_list) override;
+
+protected:
+  bool IsValid() const {
+    return m_script_object_sp && m_script_object_sp->IsValid();
+  }
+
+  lldb_private::ScriptInterpreter *m_interpreter = nullptr;
+  lldb::JITLoaderInterfaceSP m_interface_sp;
+  lldb_private::StructuredData::GenericSP m_script_object_sp;
+};
+
+#endif // LLDB_ENABLE_PYTHON
+
+#endif // liblldb_JITLoaderPython_h_
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt
index ee5e48ad5cdc3..ed254707ebb00 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt
@@ -20,6 +20,7 @@ if (LLDB_ENABLE_LIBEDIT)
 endif()
 
 add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN
+  JITLoaderPythonInterface.cpp
   OperatingSystemPythonInterface.cpp
   ScriptInterpreterPythonInterfaces.cpp
   ScriptedPlatformPythonInterface.cpp
@@ -40,5 +41,3 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN
   LINK_COMPONENTS
     Support
   )
-
-
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp
new file mode 100644
index 0000000000000..43d77964efa49
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp
@@ -0,0 +1,79 @@
+//===-- JITLoaderPythonInterface.cpp --------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Core/ModuleList.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Host/Config.h"
+#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/lldb-enumerations.h"
+
+#if LLDB_ENABLE_PYTHON
+
+// clang-format off
+// LLDB Python header must be included first
+#include "../lldb-python.h"
+//clang-format on
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "JITLoaderPythonInterface.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+JITLoaderPythonInterface::JITLoaderPythonInterface(
+    ScriptInterpreterPythonImpl &interpreter)
+    : ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+JITLoaderPythonInterface::CreatePluginObject(
+    llvm::StringRef class_name, ExecutionContext &exe_ctx) {
+  return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr,
+                                                     exe_ctx.GetProcessSP());
+}
+
+void JITLoaderPythonInterface::Initialize() {
+  const std::vector<llvm::StringRef> ci_usages = {
+      "settings set target.process.python-jit-loader-plugin-path <script-path>"};
+  const std::vector<llvm::StringRef> api_usages = {};
+  PluginManager::RegisterPlugin(
+      GetPluginNameStatic(), llvm::StringRef("JIT loader python plugin"),
+      CreateInstance, eScriptLanguagePython, {ci_usages, api_usages});
+}
+
+void JITLoaderPythonInterface::Terminate() {
+  PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+//------------------------------------------------------------------------------
+// JITLoader API overrides
+//------------------------------------------------------------------------------
+
+void JITLoaderPythonInterface::DidAttach() {
+  Status error;
+  Dispatch("did_attach", error);
+}
+
+void JITLoaderPythonInterface::DidLaunch() {
+  Status error;
+  Dispatch("did_launch", error);
+}
+
+void JITLoaderPythonInterface::ModulesDidLoad(ModuleList &module_list) {
+  Status error;
+  // There is no SBModuleList, so we need to deliver each module individually 
+  // to the python scripts since it uses the LLDB public API.
+  module_list.ForEach([&](const lldb::ModuleSP &module_sp_ref) {
+    lldb::ModuleSP module_sp(module_sp_ref);
+    Dispatch("module_did_load", error, module_sp);
+    return true; // Keep iterating  
+  });
+}
+
+#endif
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h
new file mode 100644
index 0000000000000..cb8ef5b8c43ad
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h
@@ -0,0 +1,104 @@
+//===-- JITLoaderPythonInterface.h ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_JITLOADERPYTHONINTERFACE_H
+#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_JITLOADERPYTHONINTERFACE_H
+
+#include "lldb/Host/Config.h"
+#include "lldb/Interpreter/Interfaces/JITLoaderInterface.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "ScriptedThreadPythonInterface.h"
+
+#include <optional>
+
+/// Defines a JITLoader interface for Python.
+/// 
+/// Users can implement a JIT loader in python by implementing a class that
+/// named "JITLoaderPlugin" in a module. The path to this module is specified
+/// in the settings using:
+///
+///     (lldb) setting set target.process.python-jit-loader-path <path>
+///
+/// When the process starts up it will load this module and call methods on the
+/// python class. The python class must implement the following methods:
+///
+/// #---------------------------------------------------------------------------
+/// # The class must be named "JITLoaderPlugin" in the python file.
+/// #---------------------------------------------------------------------------
+/// class JITLoaderPlugin:
+///     #-----------------------------------------------------------------------
+///     # Construct this object with reference to the process that owns this 
+///     # JIT loader.
+///     #-----------------------------------------------------------------------
+///     def __init__(self, process: lldb.SBProce...
[truncated]

@github-actions
Copy link

github-actions bot commented Jun 3, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp,h -- lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.cpp lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h lldb/test/API/functionalities/jitloader_python/jit.cpp lldb/test/API/functionalities/jitloader_python/main.cpp lldb/include/lldb/API/SBModule.h lldb/include/lldb/Interpreter/ScriptInterpreter.h lldb/include/lldb/Target/Process.h lldb/include/lldb/lldb-forward.h lldb/source/Interpreter/ScriptInterpreter.cpp lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h lldb/source/Target/Process.cpp lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
View the diff from clang-format here.
diff --git a/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h
index b18d7ae5a..84729a2c2 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/JITLoaderInterface.h
@@ -15,15 +15,13 @@
 namespace lldb_private {
 class JITLoaderInterface : virtual public ScriptedInterface {
 public:
-
   virtual llvm::Expected<StructuredData::GenericSP>
-  CreatePluginObject(llvm::StringRef class_name, 
+  CreatePluginObject(llvm::StringRef class_name,
                      lldb_private::ExecutionContext &exe_ctx) = 0;
 
   virtual void DidAttach() {};
   virtual void DidLaunch() {};
   virtual void ModulesDidLoad(lldb_private::ModuleList &module_list) {};
-
 };
 } // namespace lldb_private
 
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index f8885987f..a8027fa24 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -16,8 +16,8 @@
 #include "lldb/API/SBEvent.h"
 #include "lldb/API/SBExecutionContext.h"
 #include "lldb/API/SBLaunchInfo.h"
-#include "lldb/API/SBModule.h"
 #include "lldb/API/SBMemoryRegionInfo.h"
+#include "lldb/API/SBModule.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/Breakpoint/BreakpointOptions.h"
 #include "lldb/Core/PluginInterface.h"
@@ -560,9 +560,7 @@ public:
     return {};
   }
 
-  virtual lldb::JITLoaderInterfaceSP CreateJITLoaderInterface() {
-    return {};
-  }
+  virtual lldb::JITLoaderInterfaceSP CreateJITLoaderInterface() { return {}; }
 
   virtual lldb::ScriptedPlatformInterfaceUP GetScriptedPlatformInterface() {
     return {};
@@ -602,7 +600,7 @@ public:
       const lldb::SBExecutionContext &exe_ctx) const;
 
   lldb::ModuleSP GetOpaqueTypeFromSBModule(const lldb::SBModule &module) const;
-  
+
 protected:
   Debugger &m_debugger;
   lldb::ScriptLanguage m_script_lang;
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 0b12425c1..c95312a8f 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -130,8 +130,7 @@ ScriptInterpreter::GetOpaqueTypeFromSBExecutionContext(
   return exe_ctx.m_exe_ctx_sp;
 }
 
-lldb::ModuleSP
-ScriptInterpreter::GetOpaqueTypeFromSBModule(
+lldb::ModuleSP ScriptInterpreter::GetOpaqueTypeFromSBModule(
     const lldb::SBModule &module) const {
   return module.m_opaque_sp;
 }
diff --git a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp
index 8981d55fc..d01109b14 100644
--- a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp
+++ b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.cpp
@@ -104,14 +104,13 @@ JITLoaderPython::JITLoaderPython(lldb_private::Process *process,
   // "modulename.JITLoaderPlugin"
   os_plugin_class_name += ".JITLoaderPlugin";
 
-  JITLoaderInterfaceSP interface_sp =
-      m_interpreter->CreateJITLoaderInterface();
+  JITLoaderInterfaceSP interface_sp = m_interpreter->CreateJITLoaderInterface();
   if (!interface_sp)
     return;
 
   ExecutionContext exe_ctx(process);
-  auto obj_or_err = interface_sp->CreatePluginObject(
-      os_plugin_class_name, exe_ctx);
+  auto obj_or_err =
+      interface_sp->CreatePluginObject(os_plugin_class_name, exe_ctx);
 
   if (!obj_or_err) {
     llvm::consumeError(obj_or_err.takeError());
diff --git a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h
index 87146ad21..a86e2531d 100644
--- a/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h
+++ b/lldb/source/Plugins/JITLoader/Python/JITLoaderPython.h
@@ -28,7 +28,7 @@ public:
   ~JITLoaderPython() override;
 
   // Static Functions
-  static lldb::JITLoaderSP CreateInstance(lldb_private::Process *process, 
+  static lldb::JITLoaderSP CreateInstance(lldb_private::Process *process,
                                           bool force);
 
   static void Initialize();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h
index cb8ef5b8c..a087b4823 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/JITLoaderPythonInterface.h
@@ -19,7 +19,7 @@
 #include <optional>
 
 /// Defines a JITLoader interface for Python.
-/// 
+///
 /// Users can implement a JIT loader in python by implementing a class that
 /// named "JITLoaderPlugin" in a module. The path to this module is specified
 /// in the settings using:
@@ -34,7 +34,7 @@
 /// #---------------------------------------------------------------------------
 /// class JITLoaderPlugin:
 ///     #-----------------------------------------------------------------------
-///     # Construct this object with reference to the process that owns this 
+///     # Construct this object with reference to the process that owns this
 ///     # JIT loader.
 ///     #-----------------------------------------------------------------------
 ///     def __init__(self, process: lldb.SBProcess):
@@ -53,8 +53,8 @@
 ///         pass
 ///
 ///     #-----------------------------------------------------------------------
-///     # Called once for each module that is loaded into the debug sessions. 
-///     # This allows clients to search to symbols or references to JIT'ed 
+///     # Called once for each module that is loaded into the debug sessions.
+///     # This allows clients to search to symbols or references to JIT'ed
 ///     # functions in each module as it gets loaded. Note that this function
 ///     # can be called prior to did_attach() or did_launch() being called as
 ///     # libraries get loaded during the attach or launch.
@@ -64,30 +64,26 @@
 ///
 
 namespace lldb_private {
-class JITLoaderPythonInterface
-    : virtual public JITLoaderInterface,
-      virtual public ScriptedPythonInterface,
-      public PluginInterface {
+class JITLoaderPythonInterface : virtual public JITLoaderInterface,
+                                 virtual public ScriptedPythonInterface,
+                                 public PluginInterface {
 public:
   JITLoaderPythonInterface(ScriptInterpreterPythonImpl &interpreter);
 
   llvm::Expected<StructuredData::GenericSP>
-  CreatePluginObject(llvm::StringRef class_name, 
+  CreatePluginObject(llvm::StringRef class_name,
                      ExecutionContext &exe_ctx) override;
 
-
   llvm::SmallVector<AbstractMethodRequirement>
   GetAbstractMethodRequirements() const override {
     return llvm::SmallVector<AbstractMethodRequirement>(
-        {{"did_attach"},
-         {"did_launch"},
-         {"module_did_load", 1}});
+        {{"did_attach"}, {"did_launch"}, {"module_did_load", 1}});
   }
-          
+
   void DidAttach() override;
   void DidLaunch() override;
   void ModulesDidLoad(lldb_private::ModuleList &module_list) override;
-                   
+
   static void Initialize();
 
   static void Terminate();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index 6d2b95fd7..8291ab58d 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -180,12 +180,11 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<
 
 template <>
 lldb::ModuleSP
-ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ModuleSP>(python::PythonObject &p, Status &error) {
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ModuleSP>(
+    python::PythonObject &p, Status &error) {
 
-  lldb::SBModule *sb_module =
-      reinterpret_cast<lldb::SBModule *>(
-          python::LLDBSWIGPython_CastPyObjectToSBModule(p.get()));
+  lldb::SBModule *sb_module = reinterpret_cast<lldb::SBModule *>(
+      python::LLDBSWIGPython_CastPyObjectToSBModule(p.get()));
 
   if (!sb_module) {
     error = Status::FromErrorStringWithFormat(
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index 011cee2ea..7cc21de3f 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -594,8 +594,8 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<
 
 template <>
 lldb::ModuleSP
-ScriptedPythonInterface::ExtractValueFromPythonObject<
-    lldb::ModuleSP>(python::PythonObject &p, Status &error);
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ModuleSP>(
+    python::PythonObject &p, Status &error);
 
 } // namespace lldb_private
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 54093765f..83373a889 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1569,7 +1569,6 @@ JITLoaderInterfaceSP ScriptInterpreterPythonImpl::CreateJITLoaderInterface() {
   return std::make_shared<JITLoaderPythonInterface>(*this);
 }
 
-
 StructuredData::ObjectSP
 ScriptInterpreterPythonImpl::CreateStructuredDataFromScriptObject(
     ScriptObject obj) {
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 5f2dfed22..e7a98606d 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -213,7 +213,6 @@ FileSpec ProcessProperties::GetPythonJITLoaderPath() const {
   return GetPropertyAtIndexAs<FileSpec>(idx, {});
 }
 
-
 uint32_t ProcessProperties::GetVirtualAddressableBits() const {
   const uint32_t idx = ePropertyVirtualAddressableBits;
   return GetPropertyAtIndexAs<uint64_t>(
diff --git a/lldb/test/API/functionalities/jitloader_python/jit.cpp b/lldb/test/API/functionalities/jitloader_python/jit.cpp
index 6a8ec50e6..82e34de0b 100644
--- a/lldb/test/API/functionalities/jitloader_python/jit.cpp
+++ b/lldb/test/API/functionalities/jitloader_python/jit.cpp
@@ -3,42 +3,38 @@
 // GDB JIT interface
 enum JITAction { JIT_NOACTION, JIT_REGISTER_FN, JIT_UNREGISTER_FN };
 
-struct JITCodeEntry
-{
-    struct JITCodeEntry* next;
-    struct JITCodeEntry* prev;
-    const char *symfile_addr;
-    uint64_t symfile_size;
+struct JITCodeEntry {
+  struct JITCodeEntry *next;
+  struct JITCodeEntry *prev;
+  const char *symfile_addr;
+  uint64_t symfile_size;
 };
 
-struct JITDescriptor
-{
-    uint32_t version;
-    uint32_t action_flag;
-    struct JITCodeEntry* relevant_entry;
-    struct JITCodeEntry* first_entry;
+struct JITDescriptor {
+  uint32_t version;
+  uint32_t action_flag;
+  struct JITCodeEntry *relevant_entry;
+  struct JITCodeEntry *first_entry;
 };
 
-struct JITDescriptor __jit_debug_descriptor = { 1, JIT_NOACTION, 0, 0 };
+struct JITDescriptor __jit_debug_descriptor = {1, JIT_NOACTION, 0, 0};
 
-void __jit_debug_register_code()
-{
-}
+void __jit_debug_register_code() {}
 // end GDB JIT interface
 
 struct JITCodeEntry entry;
 
-int main()
-{
-    // Create a code entry with a bogus size
-    entry.next = entry.prev = 0;
-    entry.symfile_addr = (char *)&entry;
-    entry.symfile_size = (uint64_t)47<<32;
+int main() {
+  // Create a code entry with a bogus size
+  entry.next = entry.prev = 0;
+  entry.symfile_addr = (char *)&entry;
+  entry.symfile_size = (uint64_t)47 << 32;
 
-    __jit_debug_descriptor.relevant_entry = __jit_debug_descriptor.first_entry = &entry;
-    __jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
+  __jit_debug_descriptor.relevant_entry = __jit_debug_descriptor.first_entry =
+      &entry;
+  __jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
 
-    __jit_debug_register_code();
+  __jit_debug_register_code();
 
-    return 0;
+  return 0;
 }
diff --git a/lldb/test/API/functionalities/jitloader_python/main.cpp b/lldb/test/API/functionalities/jitloader_python/main.cpp
index 3b1d5e229..fbca82b35 100644
--- a/lldb/test/API/functionalities/jitloader_python/main.cpp
+++ b/lldb/test/API/functionalities/jitloader_python/main.cpp
@@ -1,48 +1,37 @@
 #include <inttypes.h>
 #include <stdio.h>
 
-enum JITAction { 
-  JIT_NOACTION = 0, 
-  JIT_LOAD = 1, 
-  JIT_UNLOAD = 2 
-};
+enum JITAction { JIT_NOACTION = 0, JIT_LOAD = 1, JIT_UNLOAD = 2 };
 
-struct JITEntry
-{
-    struct JITEntry* next = nullptr;
-    const char *path = nullptr;
-    uint64_t address = 0;
+struct JITEntry {
+  struct JITEntry *next = nullptr;
+  const char *path = nullptr;
+  uint64_t address = 0;
 };
 
-
 JITEntry *g_entry_list = nullptr;
 
-void jit_module_action(JITEntry *entry, JITAction action)
-{
-    printf("entry = %p, action = %i\n", (void *)entry, action);
+void jit_module_action(JITEntry *entry, JITAction action) {
+  printf("entry = %p, action = %i\n", (void *)entry, action);
 }
 // end GDB JIT interface
 
-
-int main()
-{
-    // Create an empty JITEntry. The test case will set the path and address
-    // with valid values at the first breakpoint. We build a "jit.out" binary
-    // in the python test and we will load it at a address, so the test case
-    // will calculate the path to the "jit.out" and the address to load it at
-    // and set the values with some expressions.
-    JITEntry entry;
-
-    // Call the "jit_module_action" function to cause our JIT module to be
-    // added to our target and loaded at an address.
-    jit_module_action(&entry, JIT_LOAD); // Breakpoint 1
-    printf("loaded module %s at %16.16" PRIx64 "\n", 
-           entry.path, 
-           entry.address);
-    // Call the "jit_module_action" function to cause our JIT module to be
-    // unloaded at an address.
-    jit_module_action(&entry, JIT_UNLOAD); // Breakpoint 2
-    printf("unloaded module %s" PRIx64 "\n", // Breakpoint 3
-        entry.path);
- return 0;
+int main() {
+  // Create an empty JITEntry. The test case will set the path and address
+  // with valid values at the first breakpoint. We build a "jit.out" binary
+  // in the python test and we will load it at a address, so the test case
+  // will calculate the path to the "jit.out" and the address to load it at
+  // and set the values with some expressions.
+  JITEntry entry;
+
+  // Call the "jit_module_action" function to cause our JIT module to be
+  // added to our target and loaded at an address.
+  jit_module_action(&entry, JIT_LOAD); // Breakpoint 1
+  printf("loaded module %s at %16.16" PRIx64 "\n", entry.path, entry.address);
+  // Call the "jit_module_action" function to cause our JIT module to be
+  // unloaded at an address.
+  jit_module_action(&entry, JIT_UNLOAD);   // Breakpoint 2
+  printf("unloaded module %s" PRIx64 "\n", // Breakpoint 3
+         entry.path);
+  return 0;
 }

@JDevlieghere JDevlieghere requested a review from medismailben June 3, 2025 04:20
@jimingham
Copy link
Collaborator

This seems like a generic module loading observer. I don't see anything JIT specific about it. Not saying a generic module loading observer is not a good idea. But calling it a JITLoader seems pretty confusing to me.

@jimingham
Copy link
Collaborator

This also seems like an awkward way to do what we've wanted for a while, which is the equivalent of stop hooks for "launch", "attach" and "module loaded", since you have to do all three, even if you only wanted to do one of the set.

@jimingham
Copy link
Collaborator

jimingham commented Jun 3, 2025

I changed the stop-hooks recently so they optionally fire when lldb first gets control of the process, so you can already write python code that intervenes when your "did_attach" and "did_launch" callbacks fire. So only the module_loaded callback is new.

@jimingham
Copy link
Collaborator

jimingham commented Jun 3, 2025

One thing that's nicer about your approach here over independent callbacks for each of the hooks is that it allows you to group the three callbacks in the same class instance, so that you can more easily keep shared state.

But that's a general problem with these affordances. For instance, it's super inconvenient that the summary providers and synthetic child providers produce separate objects to handle the callbacks. You end up computing the sizes of container classes twice, once for the summary and once because you need it to present the type... If the summary & child provider could share state, you'd only need to do that once per stop.

And as we are adding more of these callbacks for "lifecycle events" it would be really convenient, as you have done here, to be able to get one object to manage multiple different types of callback hits.

For the case of hooks, I wonder if we could augment the -C options we currently use to add affordances backed by a Python class with a __call__ method so you could say:

target stop-hook add -C my_python_class --shared-instance 1 --call_method stop_hook_handler

and then to finish off your design, we'd add:

target module-hook add -C my_python_class --shared-instance 1 --call_method module_hook_handler

The --shared-instance that would tell lldb to make a single object instance and reuse if for any --shared-instance addition that uses this class (one for each target in this case). Allowing you to specify the method name means you don't have to do the discrimination in __call__...

That way as we add more of these callbacks (which I agree we really need to do) people could mix and match them as the wish, and we wouldn't have to figure out the right combination(s) for everybody's use cases.

Note for summary & synthetic child providers you wouldn't need to specify the methods as those are pre-determined. So the --call-method would not be necessary. In their case the shared instance would be held by a ValueObject, since we make formatter instances for each value object we format.

It might also be handy to be able to define several commands that share state, so you could do the same thing for command classes (though in that case the shared instance would be held by the debugger not the target...)

@jimingham
Copy link
Collaborator

But if you don't care so much about shared state, then I think a better way of doing what you want is just to add target module-hook add...

Copy link
Member

@medismailben medismailben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @clayborg, this is pretty cool. I'm glad you were able to use and extend the ScriptedPythonInterface to implement this, hopefully it wasn't too complicated. LGTM!

@vogelsgesang vogelsgesang changed the title Add a pythin JIT loader class. Add a python JIT loader class. Jun 3, 2025
@bulbazord
Copy link
Member

This seems like a generic module loading observer. I don't see anything JIT specific about it. Not saying a generic module loading observer is not a good idea. But calling it a JITLoader seems pretty confusing to me.

+1 on the name. The design seems very general, so maybe calling it something like ModuleObserver or something would make more sense.

Your PR summary describes what users can do, but why might they want to do it? Do you have a motivation for this change?

@medismailben
Copy link
Member

Hey @clayborg, this is pretty cool. I'm glad you were able to use and extend the ScriptedPythonInterface to implement this, hopefully it wasn't too complicated. LGTM!

I meant LGTM on the scripting side of things. I still thing we should address Jim's comments:

For the case of hooks, I wonder if we could augment the -C options we currently use to add affordances backed by a Python class with a call method so you could say:

target stop-hook add -C my_python_class --shared-instance 1 --call_method stop_hook_handler

If you use a OptionGroupPythonClassWithDict, you automatically get -C option to specify either a class or a function to a CommandObject.

I like the idea of being able to create or share instances between scripted affordances but I think we need a way to list the existing instances (and what scripted affordance are they driving), otherwise, how would the user be able to tell what number to pass to --shared-instance ?

Suggestion: if you make this more generic, you could implement a template that's included in the lldb python module like we do for other scripted affordances (ScriptedProcess, ScriptedThread, ScriptedThreadPlan ...) with a base implementation for every function the script should implement. That would make it easier to write new scripts for this.

@medismailben medismailben self-requested a review June 3, 2025 19:37
@clayborg
Copy link
Collaborator Author

clayborg commented Jun 9, 2025

This seems like a generic module loading observer. I don't see anything JIT specific about it. Not saying a generic module loading observer is not a good idea. But calling it a JITLoader seems pretty confusing to me.

+1 on the name. The design seems very general, so maybe calling it something like ModuleObserver or something would make more sense.

Your PR summary describes what users can do, but why might they want to do it? Do you have a motivation for this change?

I do, and I should have mentioned this in the description: I want to extend the JITLoader API to be able to lazily resolve addresses. Right now JIT loaders are inefficient and expensive to use as they must stop anytime something has been JIT'ed and if a program JITs a million functions, we will stop a million times due to it setting a breakpoint in the process. I changed it so that any "load address -> lldb_private::Address" resolving goes though Target::ResolveLoadAddress() with:

commit c4fb7180cbbe977f1ab1ce945a691550f8fdd1fb
Author: Greg Clayton <[email protected]>
Date:   Tue Jan 14 20:12:46 2025 -0800

    [lldb][NFC] Make the target's SectionLoadList private. (#113278)
    
    Lots of code around LLDB was directly accessing the target's section
    load list. This NFC patch makes the section load list private so the
    Target class can access it, but everyone else now uses accessor
    functions. This allows us to control the resolving of addresses and will
    allow for functionality in LLDB which can lazily resolve addresses in
    JIT plug-ins with a future patch.

With this in, we can modify the Target::ResolveLoadAddress function like:

bool Target::ResolveLoadAddress(addr_t load_addr, Address &so_addr,
                                uint32_t stop_id, bool allow_section_end) {
  bool result = m_section_load_history.ResolveLoadAddress(stop_id, load_addr, so_addr, allow_section_end);
  if (!result) {
    // Check with JITLoader plug-ins and ask if they can resolve this address lazily.
    ...
  }
  return result;
}

This allows JIT loaders to be loaded with little or no cost until we do a backtrace. Any frames that go through JIT'ed functions that are not resolved allows the JIT loaded to load only what is needed.

Another future patch will notify the JITLoader plug-ins about breakpoints as they are created and allow the JIT loader to intelligently implement functionality to set breakpoints. We have an internal JIT solution that already does this. When a user sets a file and line breakpoint, or a breakpoint by name, the JIT loader will mark up its metadata and stop in the debugger when the code for this file and line breakpoint or breakpoint by name gets JIT'ed. Again, this helps us debug processes that JIT without the huge overhead that the current model imposes.

This patch lays the groundwork for current JITLoader plug-ins in python, and will allow me to modify the JITLoader API and test the new changes in python. It will allow me to break up the new changes piece by piece:

  • JITLoader being able to lazily resolve functions as needed when a backtrace happens without needing to have a breakpoint set all the time
  • JITLoader getting knowledge of breakpoint creation to allow breakpoints to be intelligently implemented by the JIT loader plug-in

@jimingham Hopefully this explains why this patch exists. I will read your comments above and comment if needed.

@clayborg
Copy link
Collaborator Author

clayborg commented Jun 9, 2025

This seems like a generic module loading observer. I don't see anything JIT specific about it. Not saying a generic module loading observer is not a good idea. But calling it a JITLoader seems pretty confusing to me.

true, in the current form. Check out the comments in my previous reply about the motivations for this.

One thing that's nicer about your approach here over independent callbacks for each of the hooks is that it allows you to group the three callbacks in the same class instance, so that you can more easily keep shared state.

But that's a general problem with these affordances. For instance, it's super inconvenient that the summary providers and synthetic child providers produce separate objects to handle the callbacks. You end up computing the sizes of container classes twice, once for the summary and once because you need it to present the type... If the summary & child provider could share state, you'd only need to do that once per stop.

And as we are adding more of these callbacks for "lifecycle events" it would be really convenient, as you have done here, to be able to get one object to manage multiple different types of callback hits.

For the case of hooks, I wonder if we could augment the -C options we currently use to add affordances backed by a Python class with a __call__ method so you could say:

target stop-hook add -C my_python_class --shared-instance 1 --call_method stop_hook_handler

and then to finish off your design, we'd add:

target module-hook add -C my_python_class --shared-instance 1 --call_method module_hook_handler

The --shared-instance that would tell lldb to make a single object instance and reuse if for any --shared-instance addition that uses this class (one for each target in this case). Allowing you to specify the method name means you don't have to do the discrimination in __call__...

That way as we add more of these callbacks (which I agree we really need to do) people could mix and match them as the wish, and we wouldn't have to figure out the right combination(s) for everybody's use cases.

Note for summary & synthetic child providers you wouldn't need to specify the methods as those are pre-determined. So the --call-method would not be necessary. In their case the shared instance would be held by a ValueObject, since we make formatter instances for each value object we format.

It might also be handy to be able to define several commands that share state, so you could do the same thing for command classes (though in that case the shared instance would be held by the debugger not the target...)

It would be great to create a generic version of this that allows users to add methods to a python class and just have it be notified. That being said, by motivation here is to augment the JITLoader plug-in API to do more things as mentioned above. Now we can probably do this with a notification class that we can install as long as we can find out about everything that is needed. So one approach might be to allow a python class to get all of the notifications I need for the JITLoader plug-in without calling it a JITLoader as you wanted. The class could be something like:

class TargetObserver:
    def __init__(self, target):
        # Created as soon as a target is created
        self.target = target
  
    # Module observing
    def module_added(self, module):
        # Called anytime a module is added to the target
        pass
    def module_removed(self, module):
        # Called anytime a module is removed from the target
        pass
    def module_loaded(self, module):
        # Called when the module is loaded or modified by the dynamic loader
        pass

    # Launch/Attach observing
    def did_launch(self):
        # Called when the target is done being launched
        pass
    def did_attach(self):
        # Called when the target is done being attached
        pass

    # Breakpoint observing
    def breakpoint_added(self, breakpoint):
        # Called when a breakpoint is added to the target
        pass
    def breakpoint_modified(self, breakpoint):
        # Called when a breakpoint is modified to the target
        pass
    def breakpoint_deleted(self, breakpoint):
        # Called when a breakpoint is added to the target
        pass

    # Unresolved address resolving observation
    def resolve_load_address(self, load_addr):
        # Allow plug-ins to help resolve addresses that don't resolve in the current target
        pass

We could remove the JITLoader class all together.

@jimingham
Copy link
Collaborator

jimingham commented Jun 9, 2025

This seems like a generic module loading observer. I don't see anything JIT specific about it. Not saying a generic module loading observer is not a good idea. But calling it a JITLoader seems pretty confusing to me.

true, in the current form. Check out the comments in my previous reply about the motivations for this.

One thing that's nicer about your approach here over independent callbacks for each of the hooks is that it allows you to group the three callbacks in the same class instance, so that you can more easily keep shared state.
But that's a general problem with these affordances. For instance, it's super inconvenient that the summary providers and synthetic child providers produce separate objects to handle the callbacks. You end up computing the sizes of container classes twice, once for the summary and once because you need it to present the type... If the summary & child provider could share state, you'd only need to do that once per stop.
And as we are adding more of these callbacks for "lifecycle events" it would be really convenient, as you have done here, to be able to get one object to manage multiple different types of callback hits.
For the case of hooks, I wonder if we could augment the -C options we currently use to add affordances backed by a Python class with a __call__ method so you could say:
target stop-hook add -C my_python_class --shared-instance 1 --call_method stop_hook_handler
and then to finish off your design, we'd add:
target module-hook add -C my_python_class --shared-instance 1 --call_method module_hook_handler
The --shared-instance that would tell lldb to make a single object instance and reuse if for any --shared-instance addition that uses this class (one for each target in this case). Allowing you to specify the method name means you don't have to do the discrimination in __call__...
That way as we add more of these callbacks (which I agree we really need to do) people could mix and match them as the wish, and we wouldn't have to figure out the right combination(s) for everybody's use cases.
Note for summary & synthetic child providers you wouldn't need to specify the methods as those are pre-determined. So the --call-method would not be necessary. In their case the shared instance would be held by a ValueObject, since we make formatter instances for each value object we format.
It might also be handy to be able to define several commands that share state, so you could do the same thing for command classes (though in that case the shared instance would be held by the debugger not the target...)

It would be great to create a generic version of this that allows users to add methods to a python class and just have it be notified. That being said, by motivation here is to augment the JITLoader plug-in API to do more things as mentioned above. Now we can probably do this with a notification class that we can install as long as we can find out about everything that is needed. So one approach might be to allow a python class to get all of the notifications I need for the JITLoader plug-in without calling it a JITLoader as you wanted. The class could be something like:

class TargetObserver:
    def __init__(self, target):
        # Created as soon as a target is created
        self.target = target
  
    # Module observing
    def module_added(self, module):
        # Called anytime a module is added to the target
        pass
    def module_removed(self, module):
        # Called anytime a module is removed from the target
        pass
    def module_loaded(self, module):
        # Called when the module is loaded or modified by the dynamic loader
        pass

    # Launch/Attach observing
    def did_launch(self):
        # Called when the target is done being launched
        pass
    def did_attach(self):
        # Called when the target is done being attached
        pass

    # Breakpoint observing
    def breakpoint_added(self, breakpoint):
        # Called when a breakpoint is added to the target
        pass
    def breakpoint_modified(self, breakpoint):
        # Called when a breakpoint is modified to the target
        pass
    def breakpoint_deleted(self, breakpoint):
        # Called when a breakpoint is added to the target
        pass

    # Unresolved address resolving observation
    def resolve_load_address(self, load_addr):
        # Allow plug-ins to help resolve addresses that don't resolve in the current target
        pass

We could remove the JITLoader class all together.

I think I'd come at if from the opposite direction. We don't currently know what the full set of messages that we want to send are, so making one class that receives all the messages we know about at present seems limiting.

What I was proposing instead is that when we add a way to register a callback to some event in lldb, we extend the registry to indicate not just the class that will be instantiated to watch the event, but which method is the responder.

That way, for instance, you can register a stop-hook with your class, and then you will have launch and attach callbacks already. But you need a way to say "Use a common instance of this class per-whatever entity owns that callback" and "use this method(s) on my object".

That way we don't have to hook everything up for you, but rather it will be easy for designers to make a class where they can hook up the particular callbacks they need.

@jimingham
Copy link
Collaborator

jimingham commented Jun 10, 2025

It also seems architecturally wrong to try to guess and influence what BreakpointResolvers do behind their backs. After all, the resolver might be just some Python Code you know nothing about. How would you instrument that? If I set a regular expression name breakpoint, will you know to compare that regex against what the JIT produces? What about source regular expression breakpoints? Do you figure out what the containing source file is and observe that?

Having a system where "if you set these kinds of breakpoints we'll be able to intervene, but other breakpoint types just won't work" seems awkward. If you are going to only support certain breakpoint types for JIT debugging, it seems much better to make that an explicit JIT breakpoint type writing a custom resolver that cooperates with your JIT engine to register interest and get called back when JIT events occur that are relevant to it.

Either that or we need to introduce the notion of a "dynamic symbol resolver" that you can register information about file names or symbol names, and then have the standard breakpoint resolvers check if one of these exists and registers interest for the names and files it is looking for. But trying to suss out what a resolver is going to do from the outside isn't the right way to go.

@clayborg
Copy link
Collaborator Author

It also seems architecturally wrong to try to guess and influence what BreakpointResolvers do behind their backs. After all, the resolver might be just some Python Code you know nothing about. How would you instrument that? If I set a regular expression name breakpoint, will you know to compare that regex against what the JIT produces? What about source regular expression breakpoints? Do you figure out what the containing source file is and observe that?

We currently only handle source file + line breakpoints and breakpoints by name. For source file + line breakpoints the JIT keeps metadata that says "this function contains these source file + line ranges". When we get notified that a breakpoint was set, we just need to know the source file + line, and then it allows the JIT to modify the metadata it contains if the function hasn't been JIT'ed yet. If it has been JIT'ed, it will load the debug info for that function immediately if it hasn't already been loaded. This allows the breakpoint to naturally resolve itself as soon as the debug info is loaded. If the debug info hasn't been loaded, then the modification to the metadata in the JIT marks the function as being needed by the debugger and if and when and only when it gets JIT'ed we will load the module for it and the breakpoint will naturally resolve itself. Same thing for functions by name.

Having a system where "if you set these kinds of breakpoints we'll be able to intervene, but other breakpoint types just won't work" seems awkward. If you are going to only support certain breakpoint types for JIT debugging, it seems much better to make that an explicit JIT breakpoint type writing a custom resolver that cooperates with your JIT engine to register interest and get called back when JIT events occur that are relevant to it.

We have a system that is already working just being notified about breakpoints in the JIT loader. Yes, it doesn't handle all breakpoint types right now, but we are getting this to work as proof of concept with a JIT loader that does everything lazilly.

So right now only functions that have breakpoints set in them need debug info to be generated and that is if and only if they ever get JIT'ed. It works quite well, abeit we only support two kinds of breakpoints currently. Then if a stack trace goes through a function that doesn't resolve, we can lazily load the debug info for it on the fly only when we have a backtrace that traverses through a JIT'ed frame that we don't have debug info for yet.

Either that or we need to introduce the notion of a "dynamic symbol resolver" that you can register information about file names or symbol names, and then have the standard breakpoint resolvers check if one of these exists and registers interest for the names and files it is looking for. But trying to suss out what a resolver is going to do from the outside isn't the right way to go.

Happy to meet and discuss anytime. But this PR has isn't doing any of those things yet. This just enabled python JIT loaders which we need for other purposes as well.

I think I'd come at if from the opposite direction. We don't currently know what the full set of messages that we want to send are, so making one class that receives all the messages we know about at present seems limiting.

What I was proposing instead is that when we add a way to register a callback to some event in lldb, we extend the registry to indicate not just the class that will be instantiated to watch the event, but which method is the responder.

How do we store an instance of a class and then call a method on it? We have no notion of a baton in python callbacks right now. If we allowed python callbacks to be registered with any python object as a baton, then this could be made to work, but we probably shouldn't try to call a method on some object as there is no way to specify that on the command line. We should also be able to do this with an API in the public API via callbacks with batons.

That way, for instance, you can register a stop-hook with your class, and then you will have launch and attach callbacks already. But you need a way to say "Use a common instance of this class per-whatever entity owns that callback" and "use this method(s) on my object".

That is what I don't know how to implement. How would we do this on the command line? Would we need a global variable to contain the class instance?

That way we don't have to hook everything up for you, but rather it will be easy for designers to make a class where they can hook up the particular callbacks they need.

I am fine with this as long as the solution doesn't require using command line commands to do it and we have APIs. How about public APIs where we register callbacks and make sure that when doing it through python we can specify a python object as a baton that gets given back when the callback is called. Right now all batons for python are non existent because we use the native baton for the python implementation.

@jimingham
Copy link
Collaborator

jimingham commented Jun 26, 2025

We don't have SB API's currently for the stop-hooks yet, so we're free to invent them.

But my notion was something like this:

class HookDefinition {
  void SetScriptingLanguage(lldb::ScriptLanguage lang);
  void SetScriptClassName(const char *script_class_name);
  void SetMethodName(const char *hook_method_name);
};

Then for stop hooks that have a lot of "when to stop" as opposed to "what to do when you stop" options:

// Stop Hook Filtering and AutoContinue Options
class StopHookOptions {
   SetShlibFilter
   SetFunctionNameFilter
   SetAutoContinue
   etc...
};

Then an opaque class that we will use to find the instance of the class that backs this particular hook:

class HookInstance {
  Target::HookScripted *m_hook_instance;
}

Then in Target, we'd add:

class Target {
...
HookInstance SetStopHook(HookDefinition definition, StopHookOptions &options)
...
};

Internally, lldb will do what is currently already does, instantiate an instance of script_class_name to handle this hook stash that away somewhere. But we'll add a return that allows us to look up the implementation object, that's the HookInstance class.

Then, when we hit a stop for this target, we go through the stop hook instances and dispatches to the method in hook_method_name. This is something we also already know how to do (at least for the Python scripting language), we just hard-coding the name to __call__... So that shouldn't be hard.

If we had another hook, like ModulesLoaded, then we'd do:

class Target {
...
HookInstance SetModulesLoadedHook(HookDefinition definition, ModuleLoadedHookOptions &options)
...
};

I don't know whether we'll end up needing separate "HookOptions" class, but the stop hooks can filter in a lot of ways that wouldn't make sense for module loaded, so it's likely good to separate those out.

Again, internally lldb would do the same thing it currently does for Target Stop Hooks, it would make an instance (held in the Target) of the python class and when modules were loaded, would call that instance's hook_method_name.

Then the point of the HookInstance is that we'd add to the HookDefinition:

HookDefinition::SetHookInstance(HookInstance instance)
If we make a hook using a HookDefinition with a valid instance set, then instead of making a new instance of the script_class_name to handle this hook, we'll reuse the instance returned by the call that created the HookInstance, in our list of ModulesLoaded handlers, using the hook_method_name from the HookDefinition for this new hook.

We also need to handle init methods in the case where we are sharing an instance, because you might for instance want to pass different information to set up the various hooks (e.g. different extra_args). We could do that by requiring that the implementation classes have trivial __init__ and then specify a hook-specific init that we call by hand. That shouldn't be hard.

This scheme seems doable in a language that has runtime dispatch to me. The lldb_private API layers (and their SWIG-ed equivalents) don't need to pass anything but strings: script class name, and method name, and potentially this opaque HookInstance, and lldb will manage the implementation object(s) and dispatching to them - which it already knows how to do because that's how python stop hooks work.

That way as we add more kinds of hooks, you can either choose to have each hook managed by an independent instance of your provided class, or have one instance that gets a bunch of hooks routed to it so it can aggregate data among the hooks. That would then allow you to create the composite hook handler that you want.

I'm not sure how you would provide a C++ way to do this, but maybe that's not so important. Or maybe someone better at esoteric C++ than me can think of a way to do that?

On the command-line side, we already return a hook ID when you do target stop-hook add: so you can modify the options or delete them. So we can use that as the HookInstance. So we would extend target stop-hook add to take:

(lldb) target stop-hook add --instance-type modules-loaded-hook --instance-id 1 --hook-method-name "my_method"
and route all that to the HookDefinition. This should also all be trivially wrap able in SB object, and since the interface is just strings, easily SWIG-able as well.

You seemed to have a much more complicated picture, and I wasn't sure how batons were getting involved.

Maybe I'm missing some subtlety? But this scheme seems pretty straightforward.

@jimingham
Copy link
Collaborator

To do what you want you'd also have to add:

class Target {
HookInstance SetBreakpointAddedHook(HookDefinition definition, BreakpointAddHookOptions);
};

and you can have at it.

I have no particular objection to code that's sitting not in lldb trying to figure out what breakpoint resolvers are doing from the outside. I wouldn't want that code in lldb proper because it's bound to fail in many cases, and that doesn't seem appropriate for an implementation in lldb proper. If we're going to do that then we need a more callback based approach at the SymbolContext lookup level that the resolvers can all call when they run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants